package com.mazaiting.spring.context;

import com.mazaiting.spring.annotations.Autowired;
import com.mazaiting.spring.annotations.Scope;
import com.mazaiting.spring.bean.BeanDefinition;
import com.mazaiting.spring.bean.RootBeanDefinition;
import com.mazaiting.spring.exception.ConfigException;
import com.mazaiting.spring.annotations.Component;
import com.mazaiting.spring.annotations.ComponentScan;
import com.mazaiting.util.file.FileUtil;
import com.mazaiting.util.file.StringUtil;
import javafx.scene.transform.Scale;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/***
 * 注解全局应用上下文
 * @author mazaiting
 * @datetime 2021/11/17 19:32  
 */
public class AnnotationApplicationContext implements ApplicationContext {
    /**
     * 类字节码集合, 缓存, 防止多次加载
     * key: 类的全路径名
     * value: Bean 定义
     */
    private final Map<String, BeanDefinition> beanMap = new ConcurrentHashMap<>(256);
    /**
     * 单例对象
     * key: 对象的名称
     * value: 对象实例
     */
    private final Map<String, Object> singleObjectMap = new ConcurrentHashMap<>(256);

    public AnnotationApplicationContext(Class<?> clazz) {
        init(clazz);
    }

    /**
     * 初始化方法
     *
     * @param clazz 类字节码
     * @throws ConfigException 配置异常
     */
    private void init(Class<?> clazz) throws ConfigException {
        // clazz 所在位置是否在注解路径下
        // 判断当前类是否存在ComponentScan
        if (clazz.isAnnotationPresent(ComponentScan.class)) {
            // 获取扫描包路径
            ComponentScan componentScan = clazz.getAnnotation(ComponentScan.class);
            // 获取包扫描路径
            String path = componentScan.value();
            // 获取扫描包的绝对路径
            File dir = new File(StringUtil.getClassPath(), path.replace(".", File.separator));
            // 判断这个文件或者文件路径是否存在
            if (!dir.exists()) {
                System.out.println("文件不存在");
                return;
            }
            // 这里是获取当前路径下的全部文件及文件夹
            List<File> fileList = FileUtil.listFiles(dir);
            // 遍历
            for (File tmpTFile : fileList) {
                // 如果文件不是以.class结束, 则跳过解析
                if (!tmpTFile.getName().endsWith(".class")) {
                    continue;
                }
                parseBean(tmpTFile);
            }

        } else {
            throw new ConfigException("配置类未发现@ComponentScan注解");
        }
    }

    /**
     * 解析 Bean
     * @param file 文件
     */
    private void parseBean(File file) {
        try {
            // /com/mazaiting/springdemo/ch01/Test.class
            String classFullPathName = StringUtil.getClassFullPathName(file);
            parseComponent(classFullPathName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析Component注解
     * @param fullName 全路径名
     */
    private void parseComponent(String fullName) throws Exception {
        // 加载字节码文件com.mazaiting.springdemo.ch01.Test
        BeanDefinition beanDefinition = getBeanDefinition(fullName);
        // 获取类字节码
        Class<?> clazz = beanDefinition.getClazz();
        // 判断aClass
        if (clazz.isAnnotationPresent(Component.class)) {
            Component annotation = clazz.getAnnotation(Component.class);
            // 判断属性值是否为空, 设置默认名称
            String name = StringUtil.isEmpty(annotation.value()) ?
                    StringUtil.getFirstLower(clazz.getSimpleName()) : annotation.value();
            // 创建对象
            Object o = clazz.newInstance();
            // 获取类名加入到map集合
            singleObjectMap.put(name, o);
            // 注入属性
            injectProperties(clazz, o);
        }
    }

    /**
     * 获取 Class 对象
     * @param fullName 全路径名
     * @return Class 对象
     */
    private BeanDefinition getBeanDefinition(String fullName) throws ClassNotFoundException {
        // 判断是否已经加载该类, 如果未加载就加载该类
        if (!beanMap.containsKey(fullName)) {
            // 加载类
            Class<?> clazz = Class.forName(fullName);
            // 解析设置作用域
            String scope = parseScope(clazz);
            // 构造Bean
            BeanDefinition beanDefinition = new RootBeanDefinition(clazz, scope);
            // 保存
            beanMap.put(fullName, beanDefinition);
        }
        // 获取
        return beanMap.get(fullName);
    }

    /**
     * 解析作用域
     * @param clazz 类字节码
     * @return 作用域
     */
    private String parseScope(Class<?> clazz) {
        if (clazz.isAnnotationPresent(Scope.class)){
            Scope scope = clazz.getAnnotation(Scope.class);
            return scope.value();
        }
        return "";
    }

    /**
     * 注入属性
     *
     * @param clazz 类字节码文件
     * @param tmp 当前对象
     */
    private void injectProperties(Class<?> clazz, Object tmp) throws Exception {
        // 全部属性
        Field[] fields = clazz.getDeclaredFields();
        // 遍历
        for (Field field : fields) {
            // type -> name
            // 判断是否有Autowired属性
            if (field.isAnnotationPresent(Autowired.class)) {
                // 判断单例还是原型

                // 从单例池中取对象
                Object o = singleObjectMap.get(field.getName());
                // 判断是否为空
                if (Objects.isNull(o)) {
                    // 待加载bean全路径名
                    // com.mazaiting.springdemo.ch01.TestService1 Class<?>
                    String name = field.getType().getName();
                    parseComponent(name);
                }
                // 已经获取到
                o = singleObjectMap.get(field.getName());
                field.setAccessible(true);
                field.set(tmp, o);
            }
        }
    }

    @Override
    public Object getBean(String name) {
        // 返回该对象
        Object obj = singleObjectMap.get(name);
        try {
            // 不是空继续解析
            if (Objects.nonNull(obj)) {
                String fullName = obj.getClass().getName();
                BeanDefinition beanDefinition = beanMap.get(fullName);
                // 是否是原型
                if (beanDefinition.isPrototype()) {
                    parseComponent(fullName);
                    // 重新获取
                    obj = singleObjectMap.get(name);
                }
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return obj;
    }
}
